package com.chuangke.security.service.impl;

import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import javax.servlet.http.HttpServletRequest;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import com.auth0.jwt.interfaces.Claim;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.chuangke.security.config.TokenProperties;
import com.chuangke.security.service.JwtTokenManager;
import com.chuangke.security.userdetails.JwtUserDetails;
import com.chuangke.security.util.Jwtutil;

import cn.hutool.core.util.StrUtil;

/**
 * JWT工具类
 *
 * @author chuangke
 * @date Nov 20, 2018
 */
@Service
@SuppressWarnings("rawtypes")
@ConditionalOnProperty(value="spring.cache.type",havingValue = "redis")
public class RedisJwtTokenManager implements JwtTokenManager {

    protected static final Logger logger = LoggerFactory.getLogger(RedisJwtTokenManager.class);

    @Autowired
    private RedisTemplate redisTemplate;

    @Autowired
    private TokenProperties tokenProperties;

    /**
     * 验证令牌有效期，相差不足20分钟，自动刷新缓存
     *
     * @param loginUser
     * @return 令牌
     */
    @Override
	public void verifyToken(JwtUserDetails loginUser) {
        long expireTime = loginUser.getExpireTime();
        long currentTime = System.currentTimeMillis();

        if (tokenProperties.getExpireTime() == 0) {
            return;
        }

        if (expireTime - currentTime <= (tokenProperties.getRefreshTime() * ONE_MINUTE)) {
            refreshToken(loginUser);
        }
    }

    /**
     * 刷新令牌有效期
     *
     * @param loginUser 登录信息
     */
    @Override
	@SuppressWarnings("unchecked")
    public void refreshToken(JwtUserDetails loginUser) {
        loginUser.setExpireTime(loginUser.getLoginTime() + (tokenProperties.getExpireTime() * ONE_MINUTE));
        // 根据uuid将loginUser缓存
        String uuid = getTokenKey(loginUser.getUuid());

        if (tokenProperties.getExpireTime() == 0) {
            redisTemplate.opsForValue().set(uuid, loginUser);
        } else {
            redisTemplate.opsForValue().set(uuid, loginUser, tokenProperties.getExpireTime() * ONE_MINUTE,
                    TimeUnit.MILLISECONDS);
        }

    }

    /**
     * 获取用户身份信息
     */
    @Override
	public JwtUserDetails getLoginUser(HttpServletRequest request) {
    	try {
    		 // 获取请求携带的令牌
            String token = getToken(request);
            // 解析对应的权限以及用户信息
            String uuid = getUuidByToken(token);
            String userKey = getTokenKey(uuid);

            return (JwtUserDetails) redisTemplate.opsForValue().get(userKey);
    	}catch(Exception e) {
    		logger.error(e.getMessage());
    	}
       return null ;
    }

    /**
     * 删除用户身份信息
     */
    @Override
	@SuppressWarnings("unchecked")
    public void delLoginUser(String userUuid) {
        if (!StrUtil.isBlank(userUuid)) {
            String userKey = getTokenKey(userUuid);
            redisTemplate.delete(userKey);
        }
    }

    /**
     * 获取username对应其他的所有登录uuid，不包含对当前的登录信息
     *
     * @param username
     * @param currentToken
     * @return
     */
    @Override
	public List<String> getOtherLoginUuid(String username, String currentToken) {
        String currentUuid = getUuidByToken(currentToken);

        List<JwtUserDetails> list = listOnlineUser();

        return list.stream().filter(user -> !Objects.equals(currentUuid, user.getUuid()) && username.equals(user.getUser().getName())).map(JwtUserDetails::getUuid).collect(Collectors.toList());
    }

    @Override
	public List<JwtUserDetails> listOnlineUser() {
        List<JwtUserDetails> list = new ArrayList<>();
        @SuppressWarnings("unchecked")
        Set<String> uuidSet = redisTemplate.keys(LOGIN_TOKEN_KEY + "*");
        Objects.requireNonNull(uuidSet).forEach(userKey -> list.add((JwtUserDetails) redisTemplate.opsForValue().get(userKey)));
        return list;
    }

    /**
     * 判断令牌是否过期
     *
     * @param token 令牌
     * @return 是否过期
     */
    @Override
	public Boolean isTokenExpired(String token) {
        try {
        	DecodedJWT jwt = Jwtutil.verify(tokenProperties.getSecret(), token) ;
            return jwt.getExpiresAt().before(new Date());
        } catch (Exception e) {
            return false;
        }
    }

    /**
     * 获取请求token
     *
     * @param request
     * @return
     */
    @Override
	public String getToken(HttpServletRequest request) {
        return Jwtutil.getToken(request,tokenProperties.getHeader());
    }

    /**
     * 根据token信息获取token对应的uuid
     *
     * @param token
     * @return
     */
    private String getUuidByToken(String token) {
        if (StrUtil.isBlank(token)) {
            return null;
        }

        Map<String, Claim> claims = getClaimsFromToken(token);
        if (claims == null) {
            return null;
        }

        // 解析对应的权限以及用户信息
        return claims.get(LOGIN_USER_KEY).asString();
    }

    @Override
	public String generateToken(String userLoginKey, Date expirationDate) {
        Map<String, String> claims = new HashMap<>();
        claims.put(LOGIN_USER_KEY, userLoginKey);

        return Jwtutil.getToken(tokenProperties.getSecret(),expirationDate,claims);
    }

    /**
     * 从数据声明生成令牌
     *
     * @param claims 数据声明
     * @return 令牌
     */
    @Override
	public String generateToken(Map<String, String> claims) {
        return Jwtutil.getToken(tokenProperties.getSecret(),claims);
    }

    /**
     * 从令牌中获取数据声明
     *
     * @param token 令牌
     * @return 数据声明
     */
    private Map<String, Claim> getClaimsFromToken(String token) {
    	return Jwtutil.getTokenInfo(token).getClaims() ;
    }

    private String getTokenKey(String uuid) {
        return LOGIN_TOKEN_KEY + uuid;
    }

}